﻿/***************************************************************************
 * 
 *  Copyright 2008 Anthony Tambrin
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 * 
 *  
 *  @page 				DelayedScrollBar.as
 *  @version			0.1
 *  @description		Interactive scrollbar component class. 
 * 						Features include:
 * 						1) Scroll animation
 * 
 ***************************************************************************/

package com.ui {
	
	/********************************************************
	 * Imports
	 ********************************************************/
	import flash.display.MovieClip;
	import flash.events.MouseEvent;
	import flash.events.Event;
	import flash.display.Stage;
	
	public class DelayedScrollBar extends MovieClip {
		
		/********************************************************
		 * Private Constants
		 ********************************************************/
		private static const FRAME_LABEL_UP:String = "up";
		private static const FRAME_LABEL_OVER:String = "over";
		private static const DEFAULT_INSTANCE_THUMB_BUTTON:String = "thumbButton";
		private static const DEFAULT_INSTANCE_THUMB_TRACK:String = "thumbTrack";
		private static const DEFAULT_INSTANCE_UP_BUTTON:String = "upButton";
		private static const DEFAULT_INSTANCE_DOWN_BUTTON:String = "downButton";
		private static const DEFAULT_DELAY:Number = 0.2;
		private static const SNAP_TOLERANCE:Number = 0.1;
		
		/********************************************************
		 * Private Components
		 ********************************************************/
		private var _stage:Stage;
		
		private var _container:MovieClip;
		private var _thumbButton:MovieClip; 
		private var _thumbTrack:MovieClip;
		private var _content:MovieClip; 
		private var _mask:MovieClip;
		private var _upButton:MovieClip;
		private var _downButton:MovieClip;
		
		private var _thumbButtonInstanceName:String;
		private var _thumbTrackInstanceName:String;
		private var _upButtonInstanceName:String;
		private var _downButtonInstanceName:String;
		
		/********************************************************
		 * Private Members
		 ********************************************************/
		private var _dy:Number;
		private var _contentY0:Number;
		private var _scrollStep:Number;
		private var _topBoundary:Number;
		private var _bottomBoundary:Number;
		private var _maxThumbHeight:Number;
		private var _thumbButtonRatio:Number;
		private var _contentRatio:Number;
		private var _trueTrackHeight:Number;
		private var _trueContentMovement:Number;
		private var _isFixedThumbHeight:Boolean;
		private var _isThumbPressed:Boolean;
		private var _targetThumbY:Number;
		private var _delay:Number;
		private var _tempY:Number;
		private var _moveRatio:Number;
	
		/********************************************************
		 * Constructor
		 ********************************************************/
		public function DelayedScrollBar(p_scrollbar:MovieClip, p_content:MovieClip, p_mask:MovieClip, p_initObject:Object=null) {
			p_initObject = p_initObject ? p_initObject : new Object();
	  		_isFixedThumbHeight = p_initObject.isFixedThumbHeight ? p_initObject.isFixedThumbHeight : true;
	  		_thumbButtonInstanceName = p_initObject.thumbButtonInstanceName ? p_initObject.thumbButtonInstanceName : DEFAULT_INSTANCE_THUMB_BUTTON;
	  		_thumbTrackInstanceName = p_initObject.thumbTrackInstanceName ? p_initObject.thumbTrackInstanceName : DEFAULT_INSTANCE_THUMB_TRACK;
	  		_upButtonInstanceName = p_initObject.upButtonInstanceName ? p_initObject.upButtonInstanceName : DEFAULT_INSTANCE_UP_BUTTON;
	  		_downButtonInstanceName = p_initObject.downButtonInstanceName ? p_initObject.downButtonInstanceName : DEFAULT_INSTANCE_DOWN_BUTTON;
	  
	  		_container = p_scrollbar;
			_content = p_content;
			_mask = p_mask;
			_contentY0 = _content.y;
			
			_container.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
			
			if (_container.stage) {
				mainStage = _container.stage;
			}
			
			initComponents();
			initMembers();
			
			addEventListener(Event.ENTER_FRAME, onEnterFrame);
		}
	
		/********************************************************
		 * Initialisation Methods
		 ********************************************************/
		private function initMembers():void {
			_scrollStep = 20;
			_isFixedThumbHeight = true;
			_isThumbPressed = false;
			_dy = 0;
			_delay = DEFAULT_DELAY;
			moveThumb(0);
		}
		
		private function initComponents():void {
			_thumbButton = _container[_thumbButtonInstanceName];
			_thumbTrack = _container[_thumbTrackInstanceName];
			_upButton = _container[_upButtonInstanceName];
			_downButton = _container[_downButtonInstanceName];
			
			if (!_thumbButton) throw new Error("Thumb button [" + _thumbButtonInstanceName + "] cannot be found.");
			if (!_thumbTrack) throw new Error("Thumb track [" + _thumbTrackInstanceName + "] cannot be found.");
			if (!_upButton) throw new Error("Up arrow button [" + _upButtonInstanceName + "] cannot be found.");
			if (!_downButton) throw new Error("Down arrow button [" + _downButtonInstanceName + "] cannot be found.");
			
			_maxThumbHeight = _thumbTrack.height;
			
			initThumb();
			initBoundaries();
			initTrueHeights();
			initThumbTrack();
			initArrowButtons();
		}
		
		private function initTrueHeights():void {
			_trueTrackHeight = _thumbTrack.height - _thumbButton.height;
			_trueContentMovement = (_contentY0 + _content.height) - (_mask.y + _mask.height);
		}
		
		private function initBoundaries():void {
			_topBoundary = _thumbTrack.y;
			_bottomBoundary = _topBoundary + _maxThumbHeight;
		}
		
		private function initThumbTrack():void {
			_thumbTrack.addEventListener(MouseEvent.MOUSE_DOWN, onThumbTrackPress);
		}
		
		private function initThumb():void {
			resizeThumb();
			_thumbButton.addEventListener(MouseEvent.MOUSE_DOWN, onThumbButtonPress);
			_thumbButton.addEventListener(MouseEvent.MOUSE_UP, onThumbButtonRelease);
		}
		
		private function initArrowButtons():void {
			_downButton.addEventListener(MouseEvent.MOUSE_DOWN, onDownButtonPress);
			_downButton.addEventListener(MouseEvent.MOUSE_UP, onDownButtonRelease);
			_upButton.addEventListener(MouseEvent.MOUSE_DOWN, onUpButtonPress);
			_upButton.addEventListener(MouseEvent.MOUSE_UP, onUpButtonRelease);
		}
		
		private function initStageListeners():void {
			if (!_stage) return;
		
			_stage.removeEventListener(MouseEvent.MOUSE_UP, onComponentReleaseOutside);
			_stage.removeEventListener(MouseEvent.MOUSE_MOVE, onComponentMoveOutside);
			_stage.removeEventListener(MouseEvent.MOUSE_WHEEL, onComponentWheelOutside);
			_stage.addEventListener(MouseEvent.MOUSE_UP, onComponentReleaseOutside);
			_stage.addEventListener(MouseEvent.MOUSE_MOVE, onComponentMoveOutside);
			_stage.addEventListener(MouseEvent.MOUSE_WHEEL, onComponentWheelOutside);
		}
		
		/********************************************************
		 * Accessors
		 ********************************************************/
		public function set scrollStep(p_scrollStep:Number):void {
			_scrollStep = p_scrollStep;
		}
		
		public function get isScrollRequired():Boolean {
			if (_content.height > _mask.height) return true;
			return false;	
		}
		
		public function set delay(p_delay:Number):void {
			_delay = p_delay;
		}
		public function get delay():Number {
			return _delay;
		}
		
		public function set mainStage(p_stage:Stage):void {
			_stage = p_stage;
			initStageListeners();
		}
		
		/********************************************************
		 * Private Methods
		 ********************************************************/	
		private function resetScroll():void {
			_thumbButton.visible = true;
			resizeThumb();
			initTrueHeights();
			initThumbTrack();
			
			if (_thumbButton.y + _thumbButton.height > _thumbTrack.y + _thumbTrack.height) {
				_thumbButton.y = _trueTrackHeight;
			}
		}
		
		private function resizeThumb():void {
			if (!_isFixedThumbHeight) {
				_thumbButtonRatio = _maxThumbHeight / _content.height;
				_thumbButton.height = _thumbButtonRatio*_maxThumbHeight;
			}
		}
		
		private function moveThumb(p_y:Number):void {
			if (p_y < _topBoundary) {
				p_y = _topBoundary;
			} else if (p_y + _thumbButton.height > _bottomBoundary) {
				p_y = _bottomBoundary - _thumbButton.height;
			}
			_targetThumbY = p_y;
		}
		
		/********************************************************
		 * Event Handlers
		 ********************************************************/
		private function onAddedToStage(p_event:Event):void {
			if (_container.stage) mainStage = _container.stage;
		} 
		
		private function onEnterFrame(p_event:Event):void {
			// ignore if this component is disabled
			if (!enabled) return;
			
			if (_targetThumbY == _thumbButton.y) return;
			_tempY = (_targetThumbY - _thumbButton.y) * _delay;
			_thumbButton.y += _tempY;
			_moveRatio = (_thumbButton.y - _topBoundary) / _trueTrackHeight;
			_content.y = _contentY0 - (_moveRatio * _trueContentMovement);
			if (_tempY >= -SNAP_TOLERANCE && _tempY <= SNAP_TOLERANCE) {
				_thumbButton.y = _targetThumbY;
			}
		}
		 
		private function onDownButtonPress(p_event:MouseEvent):void {
			// ignore if this component is disabled
			if (!enabled) return;
			
			_isThumbPressed = true;
			moveThumb(_targetThumbY += _scrollStep);
		}
		
		private function onDownButtonRelease(p_event:MouseEvent):void {
			// ignore if this component is disabled
			if (!enabled) return;
			
			_isThumbPressed = false;
		}
		
		private function onUpButtonPress(p_event:MouseEvent):void {
			// ignore if this component is disabled
			if (!enabled) return;
			
			_isThumbPressed = true;
			moveThumb(_targetThumbY -= _scrollStep);
		}
		
		private function onUpButtonRelease(p_event:MouseEvent):void {
			// ignore if this component is disabled
			if (!enabled) return;
			
			_isThumbPressed = false;
		}
		
		private function onThumbTrackPress(p_event:MouseEvent):void {
			// ignore if this component is disabled
			if (!enabled) return;
			
			moveThumb(p_event.stageY - (_thumbButton.height / 2) - _container.y);
		}
		
		private function onThumbButtonPress(p_event:MouseEvent):void {
			// ignore if this component is disabled
			if (!enabled) return;
			
			_isThumbPressed = true;
			_dy = p_event.stageY - _thumbButton.y;
		}
		
		private function onThumbButtonRelease(p_event:MouseEvent):void {
			// ignore if this component is disabled
			if (!enabled) return;
			
			_isThumbPressed = false;
		}
		
		private function onComponentReleaseOutside(p_event:MouseEvent):void {
			// ignore if this component is disabled
			if (!enabled) return;
			
			_isThumbPressed = false;
		}
		
		private function onComponentMoveOutside(p_event:MouseEvent):void {
			// ignore if this component is disabled
			if (!enabled) return;
			
			if (!_isThumbPressed) return;
			moveThumb(p_event.stageY - _dy);
		}
		
		private function onComponentWheelOutside(p_event:MouseEvent):void {
			// ignore if this component is disabled
			if (!enabled) return;
			
			moveThumb(_targetThumbY - p_event.delta);
		}
	}
}